home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 July / PCgo 07-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 004893.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  18.5 KB  |  527 lines

  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5.   var exceptionHandler = require('uncaught_exception_handler');
  6.   var eventNatives = requireNative('event_natives');
  7.   var logging = requireNative('logging');
  8.   var schemaRegistry = requireNative('schema_registry');
  9.   var sendRequest = require('sendRequest').sendRequest;
  10.   var utils = require('utils');
  11.   var validate = require('schemaUtils').validate;
  12.   var unloadEvent = require('unload_event');
  13.  
  14.   // Schemas for the rule-style functions on the events API that
  15.   // only need to be generated occasionally, so populate them lazily.
  16.   var ruleFunctionSchemas = {
  17.     // These values are set lazily:
  18.     // addRules: {},
  19.     // getRules: {},
  20.     // removeRules: {}
  21.   };
  22.  
  23.   // This function ensures that |ruleFunctionSchemas| is populated.
  24.   function ensureRuleSchemasLoaded() {
  25.     if (ruleFunctionSchemas.addRules)
  26.       return;
  27.     var eventsSchema = schemaRegistry.GetSchema("events");
  28.     var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
  29.  
  30.     ruleFunctionSchemas.addRules =
  31.         utils.lookup(eventType.functions, 'name', 'addRules');
  32.     ruleFunctionSchemas.getRules =
  33.         utils.lookup(eventType.functions, 'name', 'getRules');
  34.     ruleFunctionSchemas.removeRules =
  35.         utils.lookup(eventType.functions, 'name', 'removeRules');
  36.   }
  37.  
  38.   // A map of event names to the event object that is registered to that name.
  39.   var attachedNamedEvents = {};
  40.  
  41.   // An array of all attached event objects, used for detaching on unload.
  42.   var allAttachedEvents = [];
  43.  
  44.   // A map of functions that massage event arguments before they are dispatched.
  45.   // Key is event name, value is function.
  46.   var eventArgumentMassagers = {};
  47.  
  48.   // An attachment strategy for events that aren't attached to the browser.
  49.   // This applies to events with the "unmanaged" option and events without
  50.   // names.
  51.   var NullAttachmentStrategy = function(event) {
  52.     this.event_ = event;
  53.   };
  54.   NullAttachmentStrategy.prototype.onAddedListener =
  55.       function(listener) {
  56.   };
  57.   NullAttachmentStrategy.prototype.onRemovedListener =
  58.       function(listener) {
  59.   };
  60.   NullAttachmentStrategy.prototype.detach = function(manual) {
  61.   };
  62.   NullAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
  63.     // |ids| is for filtered events only.
  64.     return this.event_.listeners;
  65.   };
  66.  
  67.   // Handles adding/removing/dispatching listeners for unfiltered events.
  68.   var UnfilteredAttachmentStrategy = function(event) {
  69.     this.event_ = event;
  70.   };
  71.  
  72.   UnfilteredAttachmentStrategy.prototype.onAddedListener =
  73.       function(listener) {
  74.     // Only attach / detach on the first / last listener removed.
  75.     if (this.event_.listeners.length == 0)
  76.       eventNatives.AttachEvent(this.event_.eventName);
  77.   };
  78.  
  79.   UnfilteredAttachmentStrategy.prototype.onRemovedListener =
  80.       function(listener) {
  81.     if (this.event_.listeners.length == 0)
  82.       this.detach(true);
  83.   };
  84.  
  85.   UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
  86.     eventNatives.DetachEvent(this.event_.eventName, manual);
  87.   };
  88.  
  89.   UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
  90.     // |ids| is for filtered events only.
  91.     return this.event_.listeners;
  92.   };
  93.  
  94.   var FilteredAttachmentStrategy = function(event) {
  95.     this.event_ = event;
  96.     this.listenerMap_ = {};
  97.   };
  98.  
  99.   FilteredAttachmentStrategy.idToEventMap = {};
  100.  
  101.   FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
  102.     var id = eventNatives.AttachFilteredEvent(this.event_.eventName,
  103.                                               listener.filters || {});
  104.     if (id == -1)
  105.       throw new Error("Can't add listener");
  106.     listener.id = id;
  107.     this.listenerMap_[id] = listener;
  108.     FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
  109.   };
  110.  
  111.   FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
  112.     this.detachListener(listener, true);
  113.   };
  114.  
  115.   FilteredAttachmentStrategy.prototype.detachListener =
  116.       function(listener, manual) {
  117.     if (listener.id == undefined)
  118.       throw new Error("listener.id undefined - '" + listener + "'");
  119.     var id = listener.id;
  120.     delete this.listenerMap_[id];
  121.     delete FilteredAttachmentStrategy.idToEventMap[id];
  122.     eventNatives.DetachFilteredEvent(id, manual);
  123.   };
  124.  
  125.   FilteredAttachmentStrategy.prototype.detach = function(manual) {
  126.     for (var i in this.listenerMap_)
  127.       this.detachListener(this.listenerMap_[i], manual);
  128.   };
  129.  
  130.   FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
  131.     var result = [];
  132.     for (var i = 0; i < ids.length; i++)
  133.       $Array.push(result, this.listenerMap_[ids[i]]);
  134.     return result;
  135.   };
  136.  
  137.   function parseEventOptions(opt_eventOptions) {
  138.     function merge(dest, src) {
  139.       for (var k in src) {
  140.         if (!$Object.hasOwnProperty(dest, k)) {
  141.           dest[k] = src[k];
  142.         }
  143.       }
  144.     }
  145.  
  146.     var options = opt_eventOptions || {};
  147.     merge(options, {
  148.       // Event supports adding listeners with filters ("filtered events"), for
  149.       // example as used in the webNavigation API.
  150.       //
  151.       // event.addListener(listener, [filter1, filter2]);
  152.       supportsFilters: false,
  153.  
  154.       // Events supports vanilla events. Most APIs use these.
  155.       //
  156.       // event.addListener(listener);
  157.       supportsListeners: true,
  158.  
  159.       // Event supports adding rules ("declarative events") rather than
  160.       // listeners, for example as used in the declarativeWebRequest API.
  161.       //
  162.       // event.addRules([rule1, rule2]);
  163.       supportsRules: false,
  164.  
  165.       // Event is unmanaged in that the browser has no knowledge of its
  166.       // existence; it's never invoked, doesn't keep the renderer alive, and
  167.       // the bindings system has no knowledge of it.
  168.       //
  169.       // Both events created by user code (new chrome.Event()) and messaging
  170.       // events are unmanaged, though in the latter case the browser *does*
  171.       // interact indirectly with them via IPCs written by hand.
  172.       unmanaged: false,
  173.     });
  174.     return options;
  175.   };
  176.  
  177.   // Event object.  If opt_eventName is provided, this object represents
  178.   // the unique instance of that named event, and dispatching an event
  179.   // with that name will route through this object's listeners. Note that
  180.   // opt_eventName is required for events that support rules.
  181.   //
  182.   // Example:
  183.   //   var Event = require('event_bindings').Event;
  184.   //   chrome.tabs.onChanged = new Event("tab-changed");
  185.   //   chrome.tabs.onChanged.addListener(function(data) { alert(data); });
  186.   //   Event.dispatch("tab-changed", "hi");
  187.   // will result in an alert dialog that says 'hi'.
  188.   //
  189.   // If opt_eventOptions exists, it is a dictionary that contains the boolean
  190.   // entries "supportsListeners" and "supportsRules".
  191.   // If opt_webViewInstanceId exists, it is an integer uniquely identifying a
  192.   // <webview> tag within the embedder. If it does not exist, then this is an
  193.   // extension event rather than a <webview> event.
  194.   var EventImpl = function(opt_eventName, opt_argSchemas, opt_eventOptions,
  195.                            opt_webViewInstanceId) {
  196.     this.eventName = opt_eventName;
  197.     this.argSchemas = opt_argSchemas;
  198.     this.listeners = [];
  199.     this.eventOptions = parseEventOptions(opt_eventOptions);
  200.     this.webViewInstanceId = opt_webViewInstanceId || 0;
  201.  
  202.     if (!this.eventName) {
  203.       if (this.eventOptions.supportsRules)
  204.         throw new Error("Events that support rules require an event name.");
  205.       // Events without names cannot be managed by the browser by definition
  206.       // (the browser has no way of identifying them).
  207.       this.eventOptions.unmanaged = true;
  208.     }
  209.  
  210.     // Track whether the event has been destroyed to help track down the cause
  211.     // of http://crbug.com/258526.
  212.     // This variable will eventually hold the stack trace of the destroy call.
  213.     // TODO(kalman): Delete this and replace with more sound logic that catches
  214.     // when events are used without being *attached*.
  215.     this.destroyed = null;
  216.  
  217.     if (this.eventOptions.unmanaged)
  218.       this.attachmentStrategy = new NullAttachmentStrategy(this);
  219.     else if (this.eventOptions.supportsFilters)
  220.       this.attachmentStrategy = new FilteredAttachmentStrategy(this);
  221.     else
  222.       this.attachmentStrategy = new UnfilteredAttachmentStrategy(this);
  223.   };
  224.  
  225.   // callback is a function(args, dispatch). args are the args we receive from
  226.   // dispatchEvent(), and dispatch is a function(args) that dispatches args to
  227.   // its listeners.
  228.   function registerArgumentMassager(name, callback) {
  229.     if (eventArgumentMassagers[name])
  230.       throw new Error("Massager already registered for event: " + name);
  231.     eventArgumentMassagers[name] = callback;
  232.   }
  233.  
  234.   // Dispatches a named event with the given argument array. The args array is
  235.   // the list of arguments that will be sent to the event callback.
  236.   function dispatchEvent(name, args, filteringInfo) {
  237.     var listenerIDs = [];
  238.  
  239.     if (filteringInfo)
  240.       listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo);
  241.  
  242.     var event = attachedNamedEvents[name];
  243.     if (!event)
  244.       return;
  245.  
  246.     var dispatchArgs = function(args) {
  247.       var result = event.dispatch_(args, listenerIDs);
  248.       if (result)
  249.         logging.DCHECK(!result.validationErrors, result.validationErrors);
  250.       return result;
  251.     };
  252.  
  253.     if (eventArgumentMassagers[name])
  254.       eventArgumentMassagers[name](args, dispatchArgs);
  255.     else
  256.       dispatchArgs(args);
  257.   }
  258.  
  259.   // Registers a callback to be called when this event is dispatched.
  260.   EventImpl.prototype.addListener = function(cb, filters) {
  261.     if (!this.eventOptions.supportsListeners)
  262.       throw new Error("This event does not support listeners.");
  263.     if (this.eventOptions.maxListeners &&
  264.         this.getListenerCount_() >= this.eventOptions.maxListeners) {
  265.       throw new Error("Too many listeners for " + this.eventName);
  266.     }
  267.     if (filters) {
  268.       if (!this.eventOptions.supportsFilters)
  269.         throw new Error("This event does not support filters.");
  270.       if (filters.url && !(filters.url instanceof Array))
  271.         throw new Error("filters.url should be an array.");
  272.       if (filters.serviceType &&
  273.           !(typeof filters.serviceType === 'string')) {
  274.         throw new Error("filters.serviceType should be a string.")
  275.       }
  276.     }
  277.     var listener = {callback: cb, filters: filters};
  278.     this.attach_(listener);
  279.     $Array.push(this.listeners, listener);
  280.   };
  281.  
  282.   EventImpl.prototype.attach_ = function(listener) {
  283.     this.attachmentStrategy.onAddedListener(listener);
  284.  
  285.     if (this.listeners.length == 0) {
  286.       allAttachedEvents[allAttachedEvents.length] = this;
  287.       if (this.eventName) {
  288.         if (attachedNamedEvents[this.eventName]) {
  289.           throw new Error("Event '" + this.eventName +
  290.                           "' is already attached.");
  291.         }
  292.         attachedNamedEvents[this.eventName] = this;
  293.       }
  294.     }
  295.   };
  296.  
  297.   // Unregisters a callback.
  298.   EventImpl.prototype.removeListener = function(cb) {
  299.     if (!this.eventOptions.supportsListeners)
  300.       throw new Error("This event does not support listeners.");
  301.  
  302.     var idx = this.findListener_(cb);
  303.     if (idx == -1)
  304.       return;
  305.  
  306.     var removedListener = $Array.splice(this.listeners, idx, 1)[0];
  307.     this.attachmentStrategy.onRemovedListener(removedListener);
  308.  
  309.     if (this.listeners.length == 0) {
  310.       var i = $Array.indexOf(allAttachedEvents, this);
  311.       if (i >= 0)
  312.         delete allAttachedEvents[i];
  313.       if (this.eventName) {
  314.         if (!attachedNamedEvents[this.eventName]) {
  315.           throw new Error(
  316.               "Event '" + this.eventName + "' is not attached.");
  317.         }
  318.         delete attachedNamedEvents[this.eventName];
  319.       }
  320.     }
  321.   };
  322.  
  323.   // Test if the given callback is registered for this event.
  324.   EventImpl.prototype.hasListener = function(cb) {
  325.     if (!this.eventOptions.supportsListeners)
  326.       throw new Error("This event does not support listeners.");
  327.     return this.findListener_(cb) > -1;
  328.   };
  329.  
  330.   // Test if any callbacks are registered for this event.
  331.   EventImpl.prototype.hasListeners = function() {
  332.     return this.getListenerCount_() > 0;
  333.   };
  334.  
  335.   // Returns the number of listeners on this event.
  336.   EventImpl.prototype.getListenerCount_ = function() {
  337.     if (!this.eventOptions.supportsListeners)
  338.       throw new Error("This event does not support listeners.");
  339.     return this.listeners.length;
  340.   };
  341.  
  342.   // Returns the index of the given callback if registered, or -1 if not
  343.   // found.
  344.   EventImpl.prototype.findListener_ = function(cb) {
  345.     for (var i = 0; i < this.listeners.length; i++) {
  346.       if (this.listeners[i].callback == cb) {
  347.         return i;
  348.       }
  349.     }
  350.  
  351.     return -1;
  352.   };
  353.  
  354.   EventImpl.prototype.dispatch_ = function(args, listenerIDs) {
  355.     if (this.destroyed) {
  356.       throw new Error(this.eventName + ' was already destroyed at: ' +
  357.                       this.destroyed);
  358.     }
  359.     if (!this.eventOptions.supportsListeners)
  360.       throw new Error("This event does not support listeners.");
  361.  
  362.     if (this.argSchemas && logging.DCHECK_IS_ON()) {
  363.       try {
  364.         validate(args, this.argSchemas);
  365.       } catch (e) {
  366.         e.message += ' in ' + this.eventName;
  367.         throw e;
  368.       }
  369.     }
  370.  
  371.     // Make a copy of the listeners in case the listener list is modified
  372.     // while dispatching the event.
  373.     var listeners = $Array.slice(
  374.         this.attachmentStrategy.getListenersByIDs(listenerIDs));
  375.  
  376.     var results = [];
  377.     for (var i = 0; i < listeners.length; i++) {
  378.       try {
  379.         var result = this.wrapper.dispatchToListener(listeners[i].callback,
  380.                                                      args);
  381.         if (result !== undefined)
  382.           $Array.push(results, result);
  383.       } catch (e) {
  384.         exceptionHandler.handle('Error in event handler for ' +
  385.             (this.eventName ? this.eventName : '(unknown)'),
  386.           e);
  387.       }
  388.     }
  389.     if (results.length)
  390.       return {results: results};
  391.   }
  392.  
  393.   // Can be overridden to support custom dispatching.
  394.   EventImpl.prototype.dispatchToListener = function(callback, args) {
  395.     return $Function.apply(callback, null, args);
  396.   }
  397.  
  398.   // Dispatches this event object to all listeners, passing all supplied
  399.   // arguments to this function each listener.
  400.   EventImpl.prototype.dispatch = function(varargs) {
  401.     return this.dispatch_($Array.slice(arguments), undefined);
  402.   };
  403.  
  404.   // Detaches this event object from its name.
  405.   EventImpl.prototype.detach_ = function() {
  406.     this.attachmentStrategy.detach(false);
  407.   };
  408.  
  409.   EventImpl.prototype.destroy_ = function() {
  410.     this.listeners.length = 0;
  411.     this.detach_();
  412.     this.destroyed = exceptionHandler.getStackTrace();
  413.   };
  414.  
  415.   EventImpl.prototype.addRules = function(rules, opt_cb) {
  416.     if (!this.eventOptions.supportsRules)
  417.       throw new Error("This event does not support rules.");
  418.  
  419.     // Takes a list of JSON datatype identifiers and returns a schema fragment
  420.     // that verifies that a JSON object corresponds to an array of only these
  421.     // data types.
  422.     function buildArrayOfChoicesSchema(typesList) {
  423.       return {
  424.         'type': 'array',
  425.         'items': {
  426.           'choices': typesList.map(function(el) {return {'$ref': el};})
  427.         }
  428.       };
  429.     };
  430.  
  431.     // Validate conditions and actions against specific schemas of this
  432.     // event object type.
  433.     // |rules| is an array of JSON objects that follow the Rule type of the
  434.     // declarative extension APIs. |conditions| is an array of JSON type
  435.     // identifiers that are allowed to occur in the conditions attribute of each
  436.     // rule. Likewise, |actions| is an array of JSON type identifiers that are
  437.     // allowed to occur in the actions attribute of each rule.
  438.     function validateRules(rules, conditions, actions) {
  439.       var conditionsSchema = buildArrayOfChoicesSchema(conditions);
  440.       var actionsSchema = buildArrayOfChoicesSchema(actions);
  441.       $Array.forEach(rules, function(rule) {
  442.         validate([rule.conditions], [conditionsSchema]);
  443.         validate([rule.actions], [actionsSchema]);
  444.       });
  445.     };
  446.  
  447.     if (!this.eventOptions.conditions || !this.eventOptions.actions) {
  448.       throw new Error('Event ' + this.eventName + ' misses ' +
  449.                       'conditions or actions in the API specification.');
  450.     }
  451.  
  452.     validateRules(rules,
  453.                   this.eventOptions.conditions,
  454.                   this.eventOptions.actions);
  455.  
  456.     ensureRuleSchemasLoaded();
  457.     // We remove the first parameter from the validation to give the user more
  458.     // meaningful error messages.
  459.     validate([this.webViewInstanceId, rules, opt_cb],
  460.              $Array.splice(
  461.                  $Array.slice(ruleFunctionSchemas.addRules.parameters), 1));
  462.     sendRequest(
  463.       "events.addRules",
  464.       [this.eventName, this.webViewInstanceId, rules,  opt_cb],
  465.       ruleFunctionSchemas.addRules.parameters);
  466.   }
  467.  
  468.   EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
  469.     if (!this.eventOptions.supportsRules)
  470.       throw new Error("This event does not support rules.");
  471.     ensureRuleSchemasLoaded();
  472.     // We remove the first parameter from the validation to give the user more
  473.     // meaningful error messages.
  474.     validate([this.webViewInstanceId, ruleIdentifiers, opt_cb],
  475.              $Array.splice(
  476.                  $Array.slice(ruleFunctionSchemas.removeRules.parameters), 1));
  477.     sendRequest("events.removeRules",
  478.                 [this.eventName,
  479.                  this.webViewInstanceId,
  480.                  ruleIdentifiers,
  481.                  opt_cb],
  482.                 ruleFunctionSchemas.removeRules.parameters);
  483.   }
  484.  
  485.   EventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
  486.     if (!this.eventOptions.supportsRules)
  487.       throw new Error("This event does not support rules.");
  488.     ensureRuleSchemasLoaded();
  489.     // We remove the first parameter from the validation to give the user more
  490.     // meaningful error messages.
  491.     validate([this.webViewInstanceId, ruleIdentifiers, cb],
  492.              $Array.splice(
  493.                  $Array.slice(ruleFunctionSchemas.getRules.parameters), 1));
  494.  
  495.     sendRequest(
  496.       "events.getRules",
  497.       [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb],
  498.       ruleFunctionSchemas.getRules.parameters);
  499.   }
  500.  
  501.   unloadEvent.addListener(function() {
  502.     for (var i = 0; i < allAttachedEvents.length; ++i) {
  503.       var event = allAttachedEvents[i];
  504.       if (event)
  505.         event.detach_();
  506.     }
  507.   });
  508.  
  509.   var Event = utils.expose('Event', EventImpl, { functions: [
  510.     'addListener',
  511.     'removeListener',
  512.     'hasListener',
  513.     'hasListeners',
  514.     'dispatchToListener',
  515.     'dispatch',
  516.     'addRules',
  517.     'removeRules',
  518.     'getRules'
  519.   ] });
  520.  
  521.   // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc.
  522.   exports.Event = Event;
  523.  
  524.   exports.dispatchEvent = dispatchEvent;
  525.   exports.parseEventOptions = parseEventOptions;
  526.   exports.registerArgumentMassager = registerArgumentMassager;
  527.